module Tablero where

import Data.Char

data Color = Blanco | Negro deriving (Show, Eq, Ord)
type Posicion = (Char, Int) 
data Tablero = T (Posicion -> Maybe Color)

instance Show Tablero where
  show (T tablero) = 
      "   a b c d e f g h  \n" ++
      "  ----------------- \n" ++
      concatMap showFil [8,7..1] ++
      "  ----------------- \n" ++
      "   a b c d e f g h  \n"
    where
      showFil fil = show fil ++ " " ++
                    concatMap (showCol fil) ['a'..'h'] ++ "| " ++
                    show fil ++ "\n"
      showCol fil col = "|" ++ p (tablero (col,fil))
      p Nothing = " "
      p (Just Blanco) = "B"
      p (Just Negro) = "N"

-- Misc
opuestoParcial :: Color -> Color
opuestoParcial Negro = Blanco
opuestoParcial Blanco = Negro

opuesto :: Maybe Color -> Maybe Color
opuesto Nothing = Nothing
opuesto (Just Negro) = Just Blanco
opuesto (Just Blanco) = Just Negro

posiciones :: [(Char, Int)]
posiciones = [(c, x) | c <- ['a'..'h'], x <- [1..8]]

cantFichasTotales :: Tablero -> Int
cantFichasTotales tab = length [pos | pos <- posiciones, contenido pos tab /= Nothing]

cantFichasDeColor :: Color -> Tablero -> Int
cantFichasDeColor col tab = length [pos | pos <- posiciones, contenido pos tab == Just col]

colorConMasFichas :: Tablero -> Maybe Color
colorConMasFichas tab
    | fichasBlanco > fichasNegro = Just Blanco
    | fichasBlanco < fichasNegro = Just Negro
    | otherwise = Nothing
    where fichasBlanco = cantFichasDeColor Blanco tab
          fichasNegro  = cantFichasDeColor Negro tab

-- Ejercicio 1
vacio :: Tablero
vacio = T (\_ -> Nothing)

tableroInicial :: Tablero
tableroInicial = T f
  where
    f (i, j)
      | (i, j) `elem` [('d', 4), ('e', 5)] = Just Negro
      | (i, j) `elem` [('e', 4), ('d', 5)] = Just Blanco
      | otherwise = Nothing


-- Ejercicio 2
-- Posicion fuera del tablero reglamentario?
fueraTablero :: Posicion -> Bool
fueraTablero (i, j)
    | i `notElem` ['a'..'h'] || j > 8 = True
    | otherwise = False

-- Posicion dentro del tablero reglamentario?
dentroTablero :: Posicion -> Bool
dentroTablero p = not (fueraTablero p)

-- Devuelve el contenido de una posicion del tablero
contenido :: Posicion -> Tablero -> Maybe Color
contenido (i, j) (T f)
    | fueraTablero (i, j) = Nothing
    | otherwise = f (i, j)

-- Setea el contenido de una posicion del tablero
poner :: Posicion -> Color -> Tablero -> Tablero
poner (i, j) ficha (T f)
    | fueraTablero (i, j) = T f
    | otherwise = T g
  where
    g (k, l)
       | (k, l) == (i, j) = Just ficha
       | otherwise = f (k, l)


-- Ejercicio 3
desplazarFila :: Int -> Posicion -> Posicion
desplazarFila n (i, j) = (i, j + n)

desplazarColumna :: Int -> Posicion -> Posicion
desplazarColumna n (i, j) = (chr (ord i + n), j)


-- Ejercicio 4
-- Genero desplazamientos posibles segun funcion
generar :: Posicion -> (Posicion -> Posicion) -> [Posicion]
generar p f = takeWhile dentroTablero [ q | q <- iterate f p]

-- Genero desplazamientos posibles segun funcion, siempre y cuando hallan fichas de un
-- color especifico en cada posicion resultante del movimiento
generarSoloFichasColor :: Posicion -> Maybe Color -> Tablero -> (Posicion -> Posicion) -> [Posicion]
generarSoloFichasColor pos col tab f = takeWhile (\ p -> contenido p tab == col) (generar pos f)


-- Ejercicio 5
-- Lista de posiciones que deberian invertirse dada una posicion
posicionesAInvertir :: Posicion -> Tablero -> [Posicion]
posicionesAInvertir pos tab = concatMap (generarPosiciones pos tab)
                              [norte, sur, oeste, este, no, ne, so, se]
    where norte = desplazarFila (-1)
          sur   = desplazarFila 1
          oeste = desplazarColumna (-1)
          este  = desplazarColumna 1
          ne    = norte . este
          no    = norte . oeste
          se    = sur . este
          so    = sur . oeste

-- Posiciones que deberian invertirse, segun la funcion de movimiento dada
generarPosiciones :: Posicion -> Tablero -> (Posicion -> Posicion) -> [Posicion]
generarPosiciones pos tab f =
    if rodeado col tab f posList then posList
                                 else []
    where col = contenido pos tab
          posList = generarSoloFichasColor (f pos) (opuesto col) tab f

-- Dada una lista de posiciones, se fija si la siguiente ficha que seguiria la
-- secuencia es del color opuesto (y por lo tanto logramos rodear una fila)
rodeado :: Maybe Color -> Tablero -> (Posicion -> Posicion) -> [Posicion] -> Bool
rodeado Nothing _ _ _ = False
rodeado _ _ _ [] = False
rodeado col tab f pos = contenido (f (last pos)) tab == col


-- Ejercicio 6
invertirTodas :: [Posicion] -> Tablero -> Tablero
invertirTodas xs (T f) = T g
    where
    g p
       | p `elem` xs = opuesto (contenido p (T f))
       | otherwise = f p
